# Copyright (c) 2009 by Alex Leone <acleone ~at~ gmail.com>
#
# This file is part of epp.
#
# epp is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# epp is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with epp.  If not, see <http://www.gnu.org/licenses/>.

r"""
Contains the [define] tag.

Syntax:
    [define !attr1 !attr2="default value" tagname:]
        replacement for tagname, using [!attr1] and [!attr2],
        or any other [!attrs]

Attributes:
    -- Any attributes that start with '!' before tagname will be
       parsed like so:
       -- !attr (without ="..."): this is a required attribute that
                                  must be supplied when using the tag,
                                  eg [tagname attr="..."]
      -- !attr="default": set a default value for an attribute when
                          using the tag.
    -- 'add' (no value): add to the following tag, eg
                         [define add a:]
                             Added
                         Will add `added' to the value of [a]. This
                         will also add any required/default attributes
                         to [a].
    -- 'now' (no value): parse any tags in the define right now, eg
                         [define now a:]
                             [b]
                         then [b] will be replaced with the current
                         value of [b], not when the tag is used ([a])
    -- 'replace' (no value): replace any tag that's already defined.
                             (this will throw a warning normally)
    -- Any attributes that don't start with '!' will be made into
       tags, eg [define tagname="replacement"] then [tagname] will
       be replaced by `replacement'.

Outputs:
    The [define] tag itself will output ''.  Any tags that define
    defines will output their replacement, parsed for tags, at the
    point that they are used.

Examples:
    Note: s[1:] in p.parsetags(s[1:]) is to strip the leading '\n'
          from s

    >>> p = epp.Epp()
    >>> s = '''
    ... [define tag1="replacement"]
    ... [tag1]'''
    >>> print(p.parsetags(s[1:]))
    replacement
    >>> s = '''
    ... [define tag2:]
    ...  This is tag2!
    ... [tag2]'''
    >>> print(p.parsetags(s[1:]))
    This is tag2!
    >>> s = '''
    ... [define !req tag3:]
    ...  Required attr ---> [!req] <---
    ... [tag3 req="here"]'''
    >>> print(p.parsetags(s[1:]))
    Required attr ---> here <---
    >>> print(p.parsetags("[tag3]"))
    Traceback (most recent call last):
        ...
    RequiredAttrError: tag 'tag3' requires the 'req' attribute!
    >>> s = '''
    ... [define !def="blue" tag4:]
    ...  [!def]
    ... default value (blue): [tag4]
    ... non-default value (red): [tag4 def="red"]'''
    >>> print(p.parsetags(s[1:]))
    default value (blue): blue
    non-default value (red): red
    >>> # All other attributes that aren't required/default
    >>> # don't need to be specified:
    >>> # But if they aren't supplied [!attr] will be replaced
    >>> # with ''
    >>> s = '''
    ... [define tag5:]
    ...  attr1 = "[!attr1]"
    ...  attr2 = "[!attr2]"
    ... [tag5 attr1="I'm attr1!"]'''
    >>> print(p.parsetags(s[1:]))
    attr1 = "I'm attr1!"
    attr2 = ""
    >>> # using now
    >>> s = '''
    ... [define c="c is 1"]
    ... [define now a="[c]"]
    ... [define b="[c]"]
    ... [define replace c="c is 2"]
    ... [a]
    ... [b]'''
    >>> print(p.parsetags(s[1:]))
    c is 1
    c is 2
"""

import epp
import tags
from tags.tag import Tag
from tags.errors import *

tagnames = ["define"]

class DefinedTag(Tag):
    """
    Generic Replacement Functions.
    This is what define creates.
    """

    def __init__(self, tagName, output, defaults, requireds):
        """
        output: a list of tuples, (isAttr, outputstr)
        defaults: default attributes as a dict
        requireds: list of required attributes
        """
        self.tagName = tagName
        self.output = output
        self.defaults = defaults
        self.requireds = requireds

    def replace(self, eppinst, tagName, attrs):
        """
        tagName: the name this was called with
        """
        # dict of all the defined attributes
        definedAttrs = self.defaults.copy()
        for attrName, value in attrs:
            definedAttrs[attrName] = value
        for required in self.requireds:
            if required not in definedAttrs:
                raise RequiredAttrError(
                #eppinst._print_message(
                        "tag %r requires the %r attribute!"
                        % (tagName, required))
                return
        # replace all the attributes before parsing
        result = []
        for attrName, textValue, lineIndentChars in self.output:
            if textValue == None:
                # this is an attr
                textValue = ''
                if attrName in definedAttrs:
                    textValue = epp.add_indent_chars(
                                    definedAttrs[attrName], lineIndentChars)
                elif eppinst.hasDefinedAttr(attrName):
                    textValue = epp.add_indent_chars(
                            eppinst.get_defined_attr(attrName), lineIndentChars)
                else:
                    eppinst._print_message(
                            "Warning: [%s]: missing attribute %r"
                            %(tagName, attrName), 3)
            result.append(textValue)

        eppinst._defined_attrs.append(definedAttrs)
        preparse = ''.join(result)
        output = eppinst.parsetags(preparse)
        eppinst._defined_attrs.pop()
        return output


class Define(Tag):

    def replace(self, eppinst, tagName, attrs):
        """
        Defines a new tag.
        Returns: '' (no replacement text)
        """
        defaults = {}
        requireds = []
        toAdd = False
        now = False
        toReplace = False
        for attrName, value in attrs:
            if attrName.startswith('!'):
                if value == None:
                    # this is a required attribute
                    requireds.append(attrName[1:])
                else:
                    # this is a default
                    defaults[attrName[1:]] = value
            elif attrName == 'add' and value == None:
                toAdd = True
            elif attrName == 'now' and value == None:
                now = True
            elif attrName == 'replace' and value == None:
                toReplace = True
            else:
                if value != None:
                    output = []
                    lastMatchEnd = 0
                    for tagAttrMatch in epp.TAG_ATTR.finditer(value):
                        if tagAttrMatch.start() != lastMatchEnd:
                            # add the text string before this attribute tag
                            text = value[lastMatchEnd:tagAttrMatch.start()]
                            if now:
                                text = eppinst.parsetags(text)
                            output.append((None, text, ''))
                        lineIndentChars = epp.get_indent_chars(value, tagAttrMatch.start())
                        output.append(
                                (tagAttrMatch.group(1), None, lineIndentChars))
                        lastMatchEnd = tagAttrMatch.end()
                    if lastMatchEnd != len(value):
                        # add the text string before this attribute tag
                        text = value[lastMatchEnd:]
                        if now:
                            text = eppinst.parsetags(text)
                        output.append((None, text, ''))
                    if toAdd and attrName in eppinst._tags:
                        dtag = eppinst._tags[attrName]
                        dtag.output.extend(output)
                    elif len(output) > 0:
                        dtag = DefinedTag(attrName, output, defaults, requireds)
                    else:
                        dtag = Tag()
                else:
                    eppinst._print_message(
                            "Warning: [%s] %r should probably have a `:' or value"
                            % (tagName, attrName), 1)
                    if toAdd and attrName in eppinst._tags:
                        dtag = eppinst._tags[attrName]
                    else:
                        dtag = Tag()
                if (not toReplace) and attrName in eppinst._tags:
                    eppinst._print_message(
                            "Warning: [%s] redefinition of %r"
                            % (tagName, attrName), 1)
                eppinst._tags[attrName] = dtag
                defaults = {}
                requireds = []
                toAdd = False
                now = False
                toReplace = False
        return ''